1   /*
2    * Copyright (c) 1998, 2008, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.tools.example.debug.tty;
27  
28  import com.sun.jdi.*;
29  import com.sun.jdi.event.*;
30  import com.sun.jdi.request.*;
31  import com.sun.jdi.connect.*;
32  
33  import java.util.*;
34  import java.io.*;
35  
36  public class TTY implements EventNotifier {
37      EventHandler handler = null;
38  
39      /**
40       * List of Strings to execute at each stop.
41       */
42      private List<String> monitorCommands = new ArrayList<String>();
43      private int monitorCount = 0;
44  
45      /**
46       * The name of this tool.
47       */
48      private static final String progname = "jdb";
49  
50      @Override
51      public void vmStartEvent(VMStartEvent se)  {
52          Thread.yield();  // fetch output
53          MessageOutput.lnprint("VM Started:");
54      }
55  
56      @Override
57      public void vmDeathEvent(VMDeathEvent e)  {
58      }
59  
60      @Override
61      public void vmDisconnectEvent(VMDisconnectEvent e)  {
62      }
63  
64      @Override
65      public void threadStartEvent(ThreadStartEvent e)  {
66      }
67  
68      @Override
69      public void threadDeathEvent(ThreadDeathEvent e)  {
70      }
71  
72      @Override
73      public void classPrepareEvent(ClassPrepareEvent e)  {
74      }
75  
76      @Override
77      public void classUnloadEvent(ClassUnloadEvent e)  {
78      }
79  
80      @Override
81      public void breakpointEvent(BreakpointEvent be)  {
82          Thread.yield();  // fetch output
83          MessageOutput.lnprint("Breakpoint hit:");
84      }
85  
86      @Override
87      public void fieldWatchEvent(WatchpointEvent fwe)  {
88          Field field = fwe.field();
89          ObjectReference obj = fwe.object();
90          Thread.yield();  // fetch output
91  
92          if (fwe instanceof ModificationWatchpointEvent) {
93              MessageOutput.lnprint("Field access encountered before after",
94                                    new Object [] {field,
95                                                   fwe.valueCurrent(),
96                                                   ((ModificationWatchpointEvent)fwe).valueToBe()});
97          } else {
98              MessageOutput.lnprint("Field access encountered", field.toString());
99          }
100     }
101 
102     @Override
103     public void stepEvent(StepEvent se)  {
104         Thread.yield();  // fetch output
105         MessageOutput.lnprint("Step completed:");
106     }
107 
108     @Override
109     public void exceptionEvent(ExceptionEvent ee) {
110         Thread.yield();  // fetch output
111         Location catchLocation = ee.catchLocation();
112         if (catchLocation == null) {
113             MessageOutput.lnprint("Exception occurred uncaught",
114                                   ee.exception().referenceType().name());
115         } else {
116             MessageOutput.lnprint("Exception occurred caught",
117                                   new Object [] {ee.exception().referenceType().name(),
118                                                  Commands.locationString(catchLocation)});
119         }
120     }
121 
122     @Override
123     public void methodEntryEvent(MethodEntryEvent me) {
124         Thread.yield();  // fetch output
125         /*
126          * These can be very numerous, so be as efficient as possible.
127          * If we are stopping here, then we will see the normal location
128          * info printed.
129          */
130         if (me.request().suspendPolicy() != EventRequest.SUSPEND_NONE) {
131             // We are stopping; the name will be shown by the normal mechanism
132             MessageOutput.lnprint("Method entered:");
133         } else {
134             // We aren't stopping, show the name
135             MessageOutput.print("Method entered:");
136             printLocationOfEvent(me);
137         }
138     }
139 
140     @Override
141     public boolean methodExitEvent(MethodExitEvent me) {
142         Thread.yield();  // fetch output
143         /*
144          * These can be very numerous, so be as efficient as possible.
145          */
146         Method mmm = Env.atExitMethod();
147         Method meMethod = me.method();
148 
149         if (mmm == null || mmm.equals(meMethod)) {
150             // Either we are not tracing a specific method, or we are
151             // and we are exitting that method.
152 
153             if (me.request().suspendPolicy() != EventRequest.SUSPEND_NONE) {
154                 // We will be stopping here, so do a newline
155                 MessageOutput.println();
156             }
157             if (Env.vm().canGetMethodReturnValues()) {
158                 MessageOutput.print("Method exitedValue:", me.returnValue() + "");
159             } else {
160                 MessageOutput.print("Method exited:");
161             }
162 
163             if (me.request().suspendPolicy() == EventRequest.SUSPEND_NONE) {
164                 // We won't be stopping here, so show the method name
165                 printLocationOfEvent(me);
166 
167             }
168 
169             // In case we want to have a one shot trace exit some day, this
170             // code disables the request so we don't hit it again.
171             if (false) {
172                 // This is a one shot deal; we don't want to stop
173                 // here the next time.
174                 Env.setAtExitMethod(null);
175                 EventRequestManager erm = Env.vm().eventRequestManager();
176                 for (EventRequest eReq : erm.methodExitRequests()) {
177                     if (eReq.equals(me.request())) {
178                         eReq.disable();
179                     }
180                 }
181             }
182             return true;
183         }
184 
185         // We are tracing a specific method, and this isn't it.  Keep going.
186         return false;
187     }
188 
189     @Override
190     public void vmInterrupted() {
191         Thread.yield();  // fetch output
192         printCurrentLocation();
193         for (String cmd : monitorCommands) {
194             StringTokenizer t = new StringTokenizer(cmd);
195             t.nextToken();  // get rid of monitor number
196             executeCommand(t);
197         }
198         MessageOutput.printPrompt();
199     }
200 
201     @Override
202     public void receivedEvent(Event event) {
203     }
204 
205     private void printBaseLocation(String threadName, Location loc) {
206         MessageOutput.println("location",
207                               new Object [] {threadName,
208                                              Commands.locationString(loc)});
209     }
210 
211     private void printCurrentLocation() {
212         ThreadInfo threadInfo = ThreadInfo.getCurrentThreadInfo();
213         StackFrame frame;
214         try {
215             frame = threadInfo.getCurrentFrame();
216         } catch (IncompatibleThreadStateException exc) {
217             MessageOutput.println("<location unavailable>");
218             return;
219         }
220         if (frame == null) {
221             MessageOutput.println("No frames on the current call stack");
222         } else {
223             Location loc = frame.location();
224             printBaseLocation(threadInfo.getThread().name(), loc);
225             // Output the current source line, if possible
226             if (loc.lineNumber() != -1) {
227                 String line;
228                 try {
229                     line = Env.sourceLine(loc, loc.lineNumber());
230                 } catch (java.io.IOException e) {
231                     line = null;
232                 }
233                 if (line != null) {
234                     MessageOutput.println("source line number and line",
235                                           new Object [] {new Integer(loc.lineNumber()),
236                                                          line});
237                 }
238             }
239         }
240         MessageOutput.println();
241     }
242 
243     private void printLocationOfEvent(LocatableEvent theEvent) {
244         printBaseLocation(theEvent.thread().name(), theEvent.location());
245     }
246 
247     void help() {
248         MessageOutput.println("zz help text");
249     }
250 
251     private static final String[][] commandList = {
252         /*
253          * NOTE: this list must be kept sorted in ascending ASCII
254          *       order by element [0].  Ref: isCommand() below.
255          *
256          *Command      OK when        OK when
257          * name      disconnected?   readonly?
258          *------------------------------------
259          */
260         {"!!",           "n",         "y"},
261         {"?",            "y",         "y"},
262         {"bytecodes",    "n",         "y"},
263         {"catch",        "y",         "n"},
264         {"class",        "n",         "y"},
265         {"classes",      "n",         "y"},
266         {"classpath",    "n",         "y"},
267         {"clear",        "y",         "n"},
268         {"connectors",   "y",         "y"},
269         {"cont",         "n",         "n"},
270         {"disablegc",    "n",         "n"},
271         {"down",         "n",         "y"},
272         {"dump",         "n",         "y"},
273         {"enablegc",     "n",         "n"},
274         {"eval",         "n",         "y"},
275         {"exclude",      "y",         "n"},
276         {"exit",         "y",         "y"},
277         {"extension",    "n",         "y"},
278         {"fields",       "n",         "y"},
279         {"gc",           "n",         "n"},
280         {"help",         "y",         "y"},
281         {"ignore",       "y",         "n"},
282         {"interrupt",    "n",         "n"},
283         {"kill",         "n",         "n"},
284         {"lines",        "n",         "y"},
285         {"list",         "n",         "y"},
286         {"load",         "n",         "y"},
287         {"locals",       "n",         "y"},
288         {"lock",         "n",         "n"},
289         {"memory",       "n",         "y"},
290         {"methods",      "n",         "y"},
291         {"monitor",      "n",         "n"},
292         {"next",         "n",         "n"},
293         {"pop",          "n",         "n"},
294         {"print",        "n",         "y"},
295         {"quit",         "y",         "y"},
296         {"read",         "y",         "y"},
297         {"redefine",     "n",         "n"},
298         {"reenter",      "n",         "n"},
299         {"resume",       "n",         "n"},
300         {"run",          "y",         "n"},
301         {"save",         "n",         "n"},
302         {"set",          "n",         "n"},
303         {"sourcepath",   "y",         "y"},
304         {"step",         "n",         "n"},
305         {"stepi",        "n",         "n"},
306         {"stop",         "y",         "n"},
307         {"suspend",      "n",         "n"},
308         {"thread",       "n",         "y"},
309         {"threadgroup",  "n",         "y"},
310         {"threadgroups", "n",         "y"},
311         {"threadlocks",  "n",         "y"},
312         {"threads",      "n",         "y"},
313         {"trace",        "n",         "n"},
314         {"unmonitor",    "n",         "n"},
315         {"untrace",      "n",         "n"},
316         {"unwatch",      "y",         "n"},
317         {"up",           "n",         "y"},
318         {"use",          "y",         "y"},
319         {"version",      "y",         "y"},
320         {"watch",        "y",         "n"},
321         {"where",        "n",         "y"},
322         {"wherei",       "n",         "y"},
323     };
324 
325     /*
326      * Look up the command string in commandList.
327      * If found, return the index.
328      * If not found, return index < 0
329      */
330     private int isCommand(String key) {
331         //Reference: binarySearch() in java/util/Arrays.java
332         //           Adapted for use with String[][0].
333         int low = 0;
334         int high = commandList.length - 1;
335         while (low <= high) {
336             int mid = (low + high) >>> 1;
337             String midVal = commandList[mid][0];
338             int compare = midVal.compareTo(key);
339             if (compare < 0) {
340                 low = mid + 1;
341             } else if (compare > 0) {
342                 high = mid - 1;
343             }
344             else {
345                 return mid; // key found
346         }
347         }
348         return -(low + 1);  // key not found.
349     };
350 
351     /*
352      * Return true if the command is OK when disconnected.
353      */
354     private boolean isDisconnectCmd(int ii) {
355         if (ii < 0 || ii >= commandList.length) {
356             return false;
357         }
358         return (commandList[ii][1].equals("y"));
359     }
360 
361     /*
362      * Return true if the command is OK when readonly.
363      */
364     private boolean isReadOnlyCmd(int ii) {
365         if (ii < 0 || ii >= commandList.length) {
366             return false;
367         }
368         return (commandList[ii][2].equals("y"));
369     };
370 
371 
372     void executeCommand(StringTokenizer t) {
373         String cmd = t.nextToken().toLowerCase();
374         // Normally, prompt for the next command after this one is done
375         boolean showPrompt = true;
376 
377 
378         /*
379          * Anything starting with # is discarded as a no-op or 'comment'.
380          */
381         if (!cmd.startsWith("#")) {
382             /*
383              * Next check for an integer repetition prefix.  If found,
384              * recursively execute cmd that number of times.
385              */
386             if (Character.isDigit(cmd.charAt(0)) && t.hasMoreTokens()) {
387                 try {
388                     int repeat = Integer.parseInt(cmd);
389                     String subcom = t.nextToken("");
390                     while (repeat-- > 0) {
391                         executeCommand(new StringTokenizer(subcom));
392                         showPrompt = false; // Bypass the printPrompt() below.
393                     }
394                 } catch (NumberFormatException exc) {
395                     MessageOutput.println("Unrecognized command.  Try help...", cmd);
396                 }
397             } else {
398                 int commandNumber = isCommand(cmd);
399                 /*
400                  * Check for an unknown command
401                  */
402                 if (commandNumber < 0) {
403                     MessageOutput.println("Unrecognized command.  Try help...", cmd);
404                 } else if (!Env.connection().isOpen() && !isDisconnectCmd(commandNumber)) {
405                     MessageOutput.println("Command not valid until the VM is started with the run command",
406                                           cmd);
407                 } else if (Env.connection().isOpen() && !Env.vm().canBeModified() &&
408                            !isReadOnlyCmd(commandNumber)) {
409                     MessageOutput.println("Command is not supported on a read-only VM connection",
410                                           cmd);
411                 } else {
412 
413                     Commands evaluator = new Commands();
414                     try {
415                         if (cmd.equals("print")) {
416                             evaluator.commandPrint(t, false);
417                             showPrompt = false;        // asynchronous command
418                         } else if (cmd.equals("eval")) {
419                             evaluator.commandPrint(t, false);
420                             showPrompt = false;        // asynchronous command
421                         } else if (cmd.equals("set")) {
422                             evaluator.commandSet(t);
423                             showPrompt = false;        // asynchronous command
424                         } else if (cmd.equals("dump")) {
425                             evaluator.commandPrint(t, true);
426                             showPrompt = false;        // asynchronous command
427                         } else if (cmd.equals("locals")) {
428                             evaluator.commandLocals();
429                         } else if (cmd.equals("classes")) {
430                             evaluator.commandClasses();
431                         } else if (cmd.equals("class")) {
432                             evaluator.commandClass(t);
433                         } else if (cmd.equals("connectors")) {
434                             evaluator.commandConnectors(Bootstrap.virtualMachineManager());
435                         } else if (cmd.equals("methods")) {
436                             evaluator.commandMethods(t);
437                         } else if (cmd.equals("fields")) {
438                             evaluator.commandFields(t);
439                         } else if (cmd.equals("threads")) {
440                             evaluator.commandThreads(t);
441                         } else if (cmd.equals("thread")) {
442                             evaluator.commandThread(t);
443                         } else if (cmd.equals("suspend")) {
444                             evaluator.commandSuspend(t);
445                         } else if (cmd.equals("resume")) {
446                             evaluator.commandResume(t);
447                         } else if (cmd.equals("cont")) {
448                             evaluator.commandCont();
449                         } else if (cmd.equals("threadgroups")) {
450                             evaluator.commandThreadGroups();
451                         } else if (cmd.equals("threadgroup")) {
452                             evaluator.commandThreadGroup(t);
453                         } else if (cmd.equals("catch")) {
454                             evaluator.commandCatchException(t);
455                         } else if (cmd.equals("ignore")) {
456                             evaluator.commandIgnoreException(t);
457                         } else if (cmd.equals("step")) {
458                             evaluator.commandStep(t);
459                         } else if (cmd.equals("stepi")) {
460                             evaluator.commandStepi();
461                         } else if (cmd.equals("next")) {
462                             evaluator.commandNext();
463                         } else if (cmd.equals("kill")) {
464                             evaluator.commandKill(t);
465                         } else if (cmd.equals("interrupt")) {
466                             evaluator.commandInterrupt(t);
467                         } else if (cmd.equals("trace")) {
468                             evaluator.commandTrace(t);
469                         } else if (cmd.equals("untrace")) {
470                             evaluator.commandUntrace(t);
471                         } else if (cmd.equals("where")) {
472                             evaluator.commandWhere(t, false);
473                         } else if (cmd.equals("wherei")) {
474                             evaluator.commandWhere(t, true);
475                         } else if (cmd.equals("up")) {
476                             evaluator.commandUp(t);
477                         } else if (cmd.equals("down")) {
478                             evaluator.commandDown(t);
479                         } else if (cmd.equals("load")) {
480                             evaluator.commandLoad(t);
481                         } else if (cmd.equals("run")) {
482                             evaluator.commandRun(t);
483                             /*
484                              * Fire up an event handler, if the connection was just
485                              * opened. Since this was done from the run command
486                              * we don't stop the VM on its VM start event (so
487                              * arg 2 is false).
488                              */
489                             if ((handler == null) && Env.connection().isOpen()) {
490                                 handler = new EventHandler(this, false);
491                             }
492                         } else if (cmd.equals("memory")) {
493                             evaluator.commandMemory();
494                         } else if (cmd.equals("gc")) {
495                             evaluator.commandGC();
496                         } else if (cmd.equals("stop")) {
497                             evaluator.commandStop(t);
498                         } else if (cmd.equals("clear")) {
499                             evaluator.commandClear(t);
500                         } else if (cmd.equals("watch")) {
501                             evaluator.commandWatch(t);
502                         } else if (cmd.equals("unwatch")) {
503                             evaluator.commandUnwatch(t);
504                         } else if (cmd.equals("list")) {
505                             evaluator.commandList(t);
506                         } else if (cmd.equals("lines")) { // Undocumented command: useful for testing.
507                             evaluator.commandLines(t);
508                         } else if (cmd.equals("classpath")) {
509                             evaluator.commandClasspath(t);
510                         } else if (cmd.equals("use") || cmd.equals("sourcepath")) {
511                             evaluator.commandUse(t);
512                         } else if (cmd.equals("monitor")) {
513                             monitorCommand(t);
514                         } else if (cmd.equals("unmonitor")) {
515                             unmonitorCommand(t);
516                         } else if (cmd.equals("lock")) {
517                             evaluator.commandLock(t);
518                             showPrompt = false;        // asynchronous command
519                         } else if (cmd.equals("threadlocks")) {
520                             evaluator.commandThreadlocks(t);
521                         } else if (cmd.equals("disablegc")) {
522                             evaluator.commandDisableGC(t);
523                             showPrompt = false;        // asynchronous command
524                         } else if (cmd.equals("enablegc")) {
525                             evaluator.commandEnableGC(t);
526                             showPrompt = false;        // asynchronous command
527                         } else if (cmd.equals("save")) { // Undocumented command: useful for testing.
528                             evaluator.commandSave(t);
529                             showPrompt = false;        // asynchronous command
530                         } else if (cmd.equals("bytecodes")) { // Undocumented command: useful for testing.
531                             evaluator.commandBytecodes(t);
532                         } else if (cmd.equals("redefine")) {
533                             evaluator.commandRedefine(t);
534                         } else if (cmd.equals("pop")) {
535                             evaluator.commandPopFrames(t, false);
536                         } else if (cmd.equals("reenter")) {
537                             evaluator.commandPopFrames(t, true);
538                         } else if (cmd.equals("extension")) {
539                             evaluator.commandExtension(t);
540                         } else if (cmd.equals("exclude")) {
541                             evaluator.commandExclude(t);
542                         } else if (cmd.equals("read")) {
543                             readCommand(t);
544                         } else if (cmd.equals("help") || cmd.equals("?")) {
545                             help();
546                         } else if (cmd.equals("version")) {
547                             evaluator.commandVersion(progname,
548                                                      Bootstrap.virtualMachineManager());
549                         } else if (cmd.equals("quit") || cmd.equals("exit")) {
550                             if (handler != null) {
551                                 handler.shutdown();
552                             }
553                             Env.shutdown();
554                         } else {
555                             MessageOutput.println("Unrecognized command.  Try help...", cmd);
556                         }
557                     } catch (VMCannotBeModifiedException rovm) {
558                         MessageOutput.println("Command is not supported on a read-only VM connection", cmd);
559                     } catch (UnsupportedOperationException uoe) {
560                         MessageOutput.println("Command is not supported on the target VM", cmd);
561                     } catch (VMNotConnectedException vmnse) {
562                         MessageOutput.println("Command not valid until the VM is started with the run command",
563                                               cmd);
564                     } catch (Exception e) {
565                         MessageOutput.printException("Internal exception:", e);
566                     }
567                 }
568             }
569         }
570         if (showPrompt) {
571             MessageOutput.printPrompt();
572         }
573     }
574 
575     /*
576      * Maintain a list of commands to execute each time the VM is suspended.
577      */
578     void monitorCommand(StringTokenizer t) {
579         if (t.hasMoreTokens()) {
580             ++monitorCount;
581             monitorCommands.add(monitorCount + ": " + t.nextToken(""));
582         } else {
583             for (String cmd : monitorCommands) {
584                 MessageOutput.printDirectln(cmd);// Special case: use printDirectln()
585             }
586         }
587     }
588 
589     void unmonitorCommand(StringTokenizer t) {
590         if (t.hasMoreTokens()) {
591             String monTok = t.nextToken();
592             int monNum;
593             try {
594                 monNum = Integer.parseInt(monTok);
595             } catch (NumberFormatException exc) {
596                 MessageOutput.println("Not a monitor number:", monTok);
597                 return;
598             }
599             String monStr = monTok + ":";
600             for (String cmd : monitorCommands) {
601                 StringTokenizer ct = new StringTokenizer(cmd);
602                 if (ct.nextToken().equals(monStr)) {
603                     monitorCommands.remove(cmd);
604                     MessageOutput.println("Unmonitoring", cmd);
605                     return;
606                 }
607             }
608             MessageOutput.println("No monitor numbered:", monTok);
609         } else {
610             MessageOutput.println("Usage: unmonitor <monitor#>");
611         }
612     }
613 
614 
615     void readCommand(StringTokenizer t) {
616         if (t.hasMoreTokens()) {
617             String cmdfname = t.nextToken();
618             if (!readCommandFile(new File(cmdfname))) {
619                 MessageOutput.println("Could not open:", cmdfname);
620             }
621         } else {
622             MessageOutput.println("Usage: read <command-filename>");
623         }
624     }
625 
626     /**
627      * Read and execute a command file.  Return true if the file was read
628      * else false;
629      */
630     boolean readCommandFile(File f) {
631         BufferedReader inFile = null;
632         try {
633             if (f.canRead()) {
634                 // Process initial commands.
635                 MessageOutput.println("*** Reading commands from", f.getPath());
636                 inFile = new BufferedReader(new FileReader(f));
637                 String ln;
638                 while ((ln = inFile.readLine()) != null) {
639                     StringTokenizer t = new StringTokenizer(ln);
640                     if (t.hasMoreTokens()) {
641                         executeCommand(t);
642                     }
643                 }
644             }
645         } catch (IOException e) {
646         } finally {
647             if (inFile != null) {
648                 try {
649                     inFile.close();
650                 } catch (Exception exc) {
651                 }
652             }
653         }
654         return inFile != null;
655     }
656 
657     /**
658      * Try to read commands from dir/fname, unless
659      * the canonical path passed in is the same as that
660      * for dir/fname.
661      * Return null if that file doesn't exist,
662      * else return the canonical path of that file.
663      */
664     String readStartupCommandFile(String dir, String fname, String canonPath) {
665         File dotInitFile = new File(dir, fname);
666         if (!dotInitFile.exists()) {
667             return null;
668         }
669 
670         String myCanonFile;
671         try {
672             myCanonFile = dotInitFile.getCanonicalPath();
673         } catch (IOException ee) {
674             MessageOutput.println("Could not open:", dotInitFile.getPath());
675             return null;
676         }
677         if (canonPath == null || !canonPath.equals(myCanonFile)) {
678             if (!readCommandFile(dotInitFile)) {
679                 MessageOutput.println("Could not open:", dotInitFile.getPath());
680             }
681         }
682         return myCanonFile;
683     }
684 
685 
686     public TTY() throws Exception {
687 
688         MessageOutput.println("Initializing progname", progname);
689 
690         if (Env.connection().isOpen() && Env.vm().canBeModified()) {
691             /*
692              * Connection opened on startup. Start event handler
693              * immediately, telling it (through arg 2) to stop on the
694              * VM start event.
695              */
696             this.handler = new EventHandler(this, true);
697         }
698         try {
699             BufferedReader in =
700                     new BufferedReader(new InputStreamReader(System.in));
701 
702             String lastLine = null;
703 
704             Thread.currentThread().setPriority(Thread.NORM_PRIORITY);
705 
706             /*
707              * Read start up files.  This mimics the behavior
708              * of gdb which will read both ~/.gdbinit and then
709              * ./.gdbinit if they exist.  We have the twist that
710              * we allow two different names, so we do this:
711              *  if ~/jdb.ini exists,
712              *      read it
713              *  else if ~/.jdbrc exists,
714              *      read it
715              *
716              *  if ./jdb.ini exists,
717              *      if it hasn't been read, read it
718              *      It could have been read above because ~ == .
719              *      or because of symlinks, ...
720              *  else if ./jdbrx exists
721              *      if it hasn't been read, read it
722              */
723             {
724                 String userHome = System.getProperty("user.home");
725                 String canonPath;
726 
727                 if ((canonPath = readStartupCommandFile(userHome, "jdb.ini", null)) == null) {
728                     // Doesn't exist, try alternate spelling
729                     canonPath = readStartupCommandFile(userHome, ".jdbrc", null);
730                 }
731 
732                 String userDir = System.getProperty("user.dir");
733                 if (readStartupCommandFile(userDir, "jdb.ini", canonPath) == null) {
734                     // Doesn't exist, try alternate spelling
735                     readStartupCommandFile(userDir, ".jdbrc", canonPath);
736                 }
737             }
738 
739             // Process interactive commands.
740             MessageOutput.printPrompt();
741             while (true) {
742                 String ln = in.readLine();
743                 if (ln == null) {
744                     MessageOutput.println("Input stream closed.");
745                     ln = "quit";
746                 }
747 
748                 if (ln.startsWith("!!") && lastLine != null) {
749                     ln = lastLine + ln.substring(2);
750                     MessageOutput.printDirectln(ln);// Special case: use printDirectln()
751                 }
752 
753                 StringTokenizer t = new StringTokenizer(ln);
754                 if (t.hasMoreTokens()) {
755                     lastLine = ln;
756                     executeCommand(t);
757                 } else {
758                     MessageOutput.printPrompt();
759                 }
760             }
761         } catch (VMDisconnectedException e) {
762             handler.handleDisconnectedException();
763         }
764     }
765 
766     private static void usage() {
767         MessageOutput.println("zz usage text", new Object [] {progname,
768                                                      File.pathSeparator});
769         System.exit(1);
770     }
771 
772     static void usageError(String messageKey) {
773         MessageOutput.println(messageKey);
774         MessageOutput.println();
775         usage();
776     }
777 
778     static void usageError(String messageKey, String argument) {
779         MessageOutput.println(messageKey, argument);
780         MessageOutput.println();
781         usage();
782     }
783 
784     private static boolean supportsSharedMemory() {
785         for (Connector connector :
786                  Bootstrap.virtualMachineManager().allConnectors()) {
787             if (connector.transport() == null) {
788                 continue;
789             }
790             if ("dt_shmem".equals(connector.transport().name())) {
791                 return true;
792             }
793         }
794         return false;
795     }
796 
797     private static String addressToSocketArgs(String address) {
798         int index = address.indexOf(':');
799         if (index != -1) {
800             String hostString = address.substring(0, index);
801             String portString = address.substring(index + 1);
802             return "hostname=" + hostString + ",port=" + portString;
803         } else {
804             return "port=" + address;
805         }
806     }
807 
808     private static boolean hasWhitespace(String string) {
809         int length = string.length();
810         for (int i = 0; i < length; i++) {
811             if (Character.isWhitespace(string.charAt(i))) {
812                 return true;
813             }
814         }
815         return false;
816     }
817 
818     private static String addArgument(String string, String argument) {
819         if (hasWhitespace(argument) || argument.indexOf(',') != -1) {
820             // Quotes were stripped out for this argument, add 'em back.
821             StringBuffer buffer = new StringBuffer(string);
822             buffer.append('"');
823             for (int i = 0; i < argument.length(); i++) {
824                 char c = argument.charAt(i);
825                 if (c == '"') {
826                     buffer.append('\\');
827                 }
828                 buffer.append(c);
829             }
830             buffer.append("\" ");
831             return buffer.toString();
832         } else {
833             return string + argument + ' ';
834         }
835     }
836 
837     public static void main(String argv[]) throws MissingResourceException {
838         String cmdLine = "";
839         String javaArgs = "";
840         int traceFlags = VirtualMachine.TRACE_NONE;
841         boolean launchImmediately = false;
842         String connectSpec = null;
843 
844         MessageOutput.textResources = ResourceBundle.getBundle
845             ("com.sun.tools.example.debug.tty.TTYResources",
846              Locale.getDefault());
847 
848         for (int i = 0; i < argv.length; i++) {
849             String token = argv[i];
850             if (token.equals("-dbgtrace")) {
851                 if ((i == argv.length - 1) ||
852                     ! Character.isDigit(argv[i+1].charAt(0))) {
853                     traceFlags = VirtualMachine.TRACE_ALL;
854                 } else {
855                     String flagStr = "";
856                     try {
857                         flagStr = argv[++i];
858                         traceFlags = Integer.decode(flagStr).intValue();
859                     } catch (NumberFormatException nfe) {
860                         usageError("dbgtrace flag value must be an integer:",
861                                    flagStr);
862                         return;
863                     }
864                 }
865             } else if (token.equals("-X")) {
866                 usageError("Use java minus X to see");
867                 return;
868             } else if (
869                    // Standard VM options passed on
870                    token.equals("-v") || token.startsWith("-v:") ||  // -v[:...]
871                    token.startsWith("-verbose") ||                  // -verbose[:...]
872                    token.startsWith("-D") ||
873                    // -classpath handled below
874                    // NonStandard options passed on
875                    token.startsWith("-X") ||
876                    // Old-style options (These should remain in place as long as
877                    //  the standard VM accepts them)
878                    token.equals("-noasyncgc") || token.equals("-prof") ||
879                    token.equals("-verify") || token.equals("-noverify") ||
880                    token.equals("-verifyremote") ||
881                    token.equals("-verbosegc") ||
882                    token.startsWith("-ms") || token.startsWith("-mx") ||
883                    token.startsWith("-ss") || token.startsWith("-oss") ) {
884 
885                 javaArgs = addArgument(javaArgs, token);
886             } else if (token.equals("-tclassic")) {
887                 usageError("Classic VM no longer supported.");
888                 return;
889             } else if (token.equals("-tclient")) {
890                 // -client must be the first one
891                 javaArgs = "-client " + javaArgs;
892             } else if (token.equals("-tserver")) {
893                 // -server must be the first one
894                 javaArgs = "-server " + javaArgs;
895             } else if (token.equals("-sourcepath")) {
896                 if (i == (argv.length - 1)) {
897                     usageError("No sourcepath specified.");
898                     return;
899                 }
900                 Env.setSourcePath(argv[++i]);
901             } else if (token.equals("-classpath")) {
902                 if (i == (argv.length - 1)) {
903                     usageError("No classpath specified.");
904                     return;
905                 }
906                 javaArgs = addArgument(javaArgs, token);
907                 javaArgs = addArgument(javaArgs, argv[++i]);
908             } else if (token.equals("-attach")) {
909                 if (connectSpec != null) {
910                     usageError("cannot redefine existing connection", token);
911                     return;
912                 }
913                 if (i == (argv.length - 1)) {
914                     usageError("No attach address specified.");
915                     return;
916                 }
917                 String address = argv[++i];
918 
919                 /*
920                  * -attach is shorthand for one of the reference implementation's
921                  * attaching connectors. Use the shared memory attach if it's
922                  * available; otherwise, use sockets. Build a connect
923                  * specification string based on this decision.
924                  */
925                 if (supportsSharedMemory()) {
926                     connectSpec = "com.sun.jdi.SharedMemoryAttach:name=" +
927                                    address;
928                 } else {
929                     String suboptions = addressToSocketArgs(address);
930                     connectSpec = "com.sun.jdi.SocketAttach:" + suboptions;
931                 }
932             } else if (token.equals("-listen") || token.equals("-listenany")) {
933                 if (connectSpec != null) {
934                     usageError("cannot redefine existing connection", token);
935                     return;
936                 }
937                 String address = null;
938                 if (token.equals("-listen")) {
939                     if (i == (argv.length - 1)) {
940                         usageError("No attach address specified.");
941                         return;
942                     }
943                     address = argv[++i];
944                 }
945 
946                 /*
947                  * -listen[any] is shorthand for one of the reference implementation's
948                  * listening connectors. Use the shared memory listen if it's
949                  * available; otherwise, use sockets. Build a connect
950                  * specification string based on this decision.
951                  */
952                 if (supportsSharedMemory()) {
953                     connectSpec = "com.sun.jdi.SharedMemoryListen:";
954                     if (address != null) {
955                         connectSpec += ("name=" + address);
956                     }
957                 } else {
958                     connectSpec = "com.sun.jdi.SocketListen:";
959                     if (address != null) {
960                         connectSpec += addressToSocketArgs(address);
961                     }
962                 }
963             } else if (token.equals("-launch")) {
964                 launchImmediately = true;
965             } else if (token.equals("-listconnectors")) {
966                 Commands evaluator = new Commands();
967                 evaluator.commandConnectors(Bootstrap.virtualMachineManager());
968                 return;
969             } else if (token.equals("-connect")) {
970                 /*
971                  * -connect allows the user to pick the connector
972                  * used in bringing up the target VM. This allows
973                  * use of connectors other than those in the reference
974                  * implementation.
975                  */
976                 if (connectSpec != null) {
977                     usageError("cannot redefine existing connection", token);
978                     return;
979                 }
980                 if (i == (argv.length - 1)) {
981                     usageError("No connect specification.");
982                     return;
983                 }
984                 connectSpec = argv[++i];
985             } else if (token.equals("-help")) {
986                 usage();
987             } else if (token.equals("-version")) {
988                 Commands evaluator = new Commands();
989                 evaluator.commandVersion(progname,
990                                          Bootstrap.virtualMachineManager());
991                 System.exit(0);
992             } else if (token.startsWith("-")) {
993                 usageError("invalid option", token);
994                 return;
995             } else {
996                 // Everything from here is part of the command line
997                 cmdLine = addArgument("", token);
998                 for (i++; i < argv.length; i++) {
999                     cmdLine = addArgument(cmdLine, argv[i]);
1000                 }
1001                 break;
1002             }
1003         }
1004 
1005         /*
1006          * Unless otherwise specified, set the default connect spec.
1007          */
1008 
1009         /*
1010          * Here are examples of jdb command lines and how the options
1011          * are interpreted as arguments to the program being debugged.
1012          * arg1       arg2
1013          * ----       ----
1014          * jdb hello a b       a          b
1015          * jdb hello "a b"     a b
1016          * jdb hello a,b       a,b
1017          * jdb hello a, b      a,         b
1018          * jdb hello "a, b"    a, b
1019          * jdb -connect "com.sun.jdi.CommandLineLaunch:main=hello  a,b"   illegal
1020          * jdb -connect  com.sun.jdi.CommandLineLaunch:main=hello "a,b"   illegal
1021          * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a,b"'  arg1 = a,b
1022          * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a b"'  arg1 = a b
1023          * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello  a b'   arg1 = a  arg2 = b
1024          * jdb -connect 'com.sun.jdi.CommandLineLaunch:main=hello "a," b' arg1 = a, arg2 = b
1025          */
1026         if (connectSpec == null) {
1027             connectSpec = "com.sun.jdi.CommandLineLaunch:";
1028         } else if (!connectSpec.endsWith(",") && !connectSpec.endsWith(":")) {
1029             connectSpec += ","; // (Bug ID 4285874)
1030         }
1031 
1032         cmdLine = cmdLine.trim();
1033         javaArgs = javaArgs.trim();
1034 
1035         if (cmdLine.length() > 0) {
1036             if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) {
1037                 usageError("Cannot specify command line with connector:",
1038                            connectSpec);
1039                 return;
1040             }
1041             connectSpec += "main=" + cmdLine + ",";
1042         }
1043 
1044         if (javaArgs.length() > 0) {
1045             if (!connectSpec.startsWith("com.sun.jdi.CommandLineLaunch:")) {
1046                 usageError("Cannot specify target vm arguments with connector:",
1047                            connectSpec);
1048                 return;
1049             }
1050             connectSpec += "options=" + javaArgs + ",";
1051         }
1052 
1053         try {
1054             if (! connectSpec.endsWith(",")) {
1055                 connectSpec += ","; // (Bug ID 4285874)
1056             }
1057             Env.init(connectSpec, launchImmediately, traceFlags);
1058             new TTY();
1059         } catch(Exception e) {
1060             MessageOutput.printException("Internal exception:", e);
1061         }
1062     }
1063 }